﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using AddIn.EA.Framework.Common;
using AddIn.EA.Framework;
using AddIn.EA.Architecture.CatalogService.BusinessEntity;
using System.IO;
using System.Xml.Serialization;
using AddIn.EA.Architecture.Validation.BusinessEntity;
using AddIn.EA.Architecture.Validation.BusinessLogic;
using AddIn.EA.Architecture.CatalogService.BusinessEntity.Enum;


namespace AddIn.EA.Architecture.CatalogService.BusinessLogic
{
    /// <summary>
    /// Classe que armazena as regras de negócio para o gerador de comentários XML
    /// </summary>
    public class CatalogXML
    {
        private RepositoryManager repository { get; set; }
        private Package service { get; set; }

        public CatalogXML(RepositoryManager repository, Package service)
        {
            this.repository = repository;
            this.service = service;
        }

        /// <summary>
        /// Gera o XML do catalogo
        /// </summary>
        /// <param name="saveFile">Se true salva o resultado em arquivo</param>
        /// <param name="pathXml">Caminho onde sera salvo o XML</param>
        /// <param name="pathAssembly">Caminho onde esta a assembly</param>
        /// <param name="xml">XML em formato texto</param>
        /// <returns>Lista de erros</returns>
        public ValidateErrorList CreateXML(bool saveFile, string pathXml, string pathAssembly, out string xml)
        {
            IValidator validator = new ServiceValidator(this.repository, this.service);
            var errors = validator.Validate();
            if (errors != null && errors.Count > 0)
            {
                xml = string.Empty;
                return errors;
            }

            // Montar caminho da assembly
            string fileAssembly = Path.Combine(pathAssembly, this.service.Element.GetFullNamespace() + ".dll");
            if (!File.Exists(fileAssembly))
                throw new CatalogServiceException("Assembly " + fileAssembly + " não encontrada");

            // Montar caminho do config da assembly
            string fileConfig = Path.Combine(pathAssembly, this.service.Element.GetFullNamespace() + ".dll.config");
            if (!File.Exists(fileConfig))
                throw new CatalogServiceException("Config " + fileConfig + " não encontrado");

            var newDoc = new Doc();
            var namespaceXml = new NamespaceXml(this.repository, this.service);
            var interfaceXml = new InterfaceXml(this.repository, this.service);
            var methodXml = new MethodXml(this.repository, this.service);
            var dataContractXml = new DataContractXml(this.repository, this.service);

            // assembly
            newDoc.Assembly = new Assembly()
            {
                Name = this.service.Element.GetFullNamespace()
            };

            // namespaces
            newDoc.Members = new List<MemberBase>();
            newDoc.Members.AddRange(namespaceXml.GetNamespaceXmls());
            
            // interface
            newDoc.Members.AddRange(interfaceXml.GetInterfaceXmls(fileConfig));

            // metodos
            newDoc.Members.AddRange(methodXml.GetMethodXmls(fileAssembly));

            // contrato de dados - enum
            var enumXmls = dataContractXml.GetEnumXmls();
            if (enumXmls != null)
                newDoc.Members.AddRange(enumXmls);

            // contrato de dados - request
            var requestXmls = dataContractXml.GetDataContractXmls("Request");
            if (requestXmls != null)
                newDoc.Members.AddRange(requestXmls);

            // contrato de dados - response
            var responseXmls = dataContractXml.GetDataContractXmls("Response");
            if (responseXmls != null)
                newDoc.Members.AddRange(responseXmls);

            Doc finalDoc;

            // Buscar arquivo XML existente
            var existingDoc = DeserializeXmlToFile(pathXml);

            // Se existir, fazer merge
            if (existingDoc != null)
                finalDoc = MergeDocs(existingDoc, newDoc);
            else
                finalDoc = newDoc;

            // Salvar em arquivo
            if (saveFile)
                SerializeXmlToFile(finalDoc, pathXml);

            // Salvar em texto
            xml = SerializeXmlToText(finalDoc);
            
            return null;
        }

        /// <summary>
        /// Faz merge entre o XML existente e o gerado
        /// </summary>
        /// <param name="existingDoc"></param>
        /// <param name="newDoc"></param>
        /// <returns></returns>
        private Doc MergeDocs(Doc existingDoc, Doc newDoc)
        {
            // Remover todos os membros do mesmo EA
            existingDoc.Members.RemoveAll(x => x.ProjectGUID == this.repository.ProjectGUID);

            // Remover os namespaces
            existingDoc.Members.RemoveAll(x => x.MemberPrefix == PrefixName.N);

            // Adicionar novamente
            existingDoc.Members.AddRange(newDoc.Members);

            return existingDoc;
        }
        
        /// <summary>
        /// Cria xml de teste
        /// </summary>
        private void CreateXMLStub()
        {
            var doc = new Doc
            {
                Assembly = new Assembly { Name = "Sinacor.Servico.Modulo.Submodulo.Service" },
                Members = new List<MemberBase>
                {
                    #region interface
                    new MemberType
                    {
                        Name = "Sinacor.Servico.Modulo.Submodulo.Service.ServiceContract.IInterface",
                        Summary = "Descrição do contrato de serviço",
                        Remarks = new Remarks
                        {
                            List = new ListBullet
                            {
                                Item = new List<Item>
                                {
                                    new Item
                                    {
                                        Description = "Url´s de acesso ao contrato",
                                        List = new ListTable
                                        {
                                            ListHeader = new Item
                                            {
                                                Term = new List<string> { "Protocolo" },
                                                Description = "URL"
                                            },
                                            Item = new List<Item>
                                            {
                                                new Item
                                                {
                                                    Term = new List<string> { "Tcp" },
                                                    Description = "Url Tcp"
                                                },
                                                new Item
                                                {
                                                    Term = new List<string> { "NamedPipe" },
                                                    Description = "Url NamedPipe"
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    #endregion
                    #region metodo
                    new MemberMethod
                    { 
                        Name = "Sinacor.Servico.Modulo.Submodulo.Service.ServiceContract.IInterface.Metodo",
                        Summary = "Descrição do método",
                        Params = new List<Param>
                        {
                            new Param { Name = "request1", Description = "Descrição do parametro 1" },
                            new Param { Name = "request2", Description = "Descrição do parametro 2" }                        
                        },
                        Returns = "Retorno do método",

                        Remarks = new Remarks 
                        { 
                            List = new ListBullet
                            {
                                Item = new List<Item>
                                {
                                    new Item
                                    {
                                        Description = "Transacional:",
                                        List = new ListNumber
                                        { 
                                            Item = new List<Item> { new Item { Description = "Sim" } }
                                        }
                                    },
                                    new Item
                                    {
                                        Description = "Exceções de negócio:",
                                        List = new ListTable
                                        {
                                            ListHeader = new Item
                                            {
                                                Term = new List<string> { "ID", "Tipo" },
                                                Description = "Condição"
                                            },
                                            Item = new List<Item>
                                            {
                                                new Item
                                                {
                                                    Term = new List<string> { "ID da exceção", "Tipo da exceção" },
                                                    Description = "Condição ao qual a exceção ocorre"
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    #endregion
                    #region contrato de dados
                    new MemberType
                    {
                        Name = "Sinacor.Servico.Modulo.Submodulo.Service.DataContract.Request.ClasseRequest",
                        Summary = "Descrição do contrato de dados",
                    },
                    #endregion
                    #region propriedade
                    new MemberProperty
                    { 
                        Name = "Sinacor.Servico.Modulo.Submodulo.Service.DataContract.Request.ClasseRequest.Propriedade",
                        Summary = "Descrição da propriedade",
                    }
                    #endregion
                }
            };
        }

        /// <summary>
        /// Deserializa xml para um objeto
        /// </summary>
        /// <param name="pathXml"></param>
        /// <returns></returns>
        private Doc DeserializeXmlToFile(string pathXml)
        {
            Doc doc;
            string path = Path.Combine(pathXml, this.service.Element.GetFullNamespace() + ".xml");

            if (!File.Exists(path))
                return null;

            using (var stream = new FileStream(path,
                FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Doc));
                doc = (Doc)serializer.Deserialize(stream);
            }

            return doc;
        }

        /// <summary>
        /// Serializa xml para um arquivo
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="pathXml"></param>
        private void SerializeXmlToFile(Doc doc, string pathXml)
        {
            string path = Path.Combine(pathXml, this.service.Element.GetFullNamespace() + ".xml");

            if (File.GetAttributes(path) == FileAttributes.ReadOnly)
                throw new CatalogServiceException("O arquivo XML gerado é somente leitura. Verifique se está com CheckOut no Source Control");

            using (var stream = new FileStream(path,
                FileMode.Create, FileAccess.Write, FileShare.Write))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Doc));
                serializer.Serialize(new XmlTextWriter(stream, Encoding.UTF8), doc);
            }
        }

        /// <summary>
        /// Serializa xml para uma string
        /// </summary>
        /// <param name="doc"></param>
        /// <returns></returns>
        private string SerializeXmlToText(Doc doc)
        {
            string xmlText;

            using (var stream = new MemoryStream())
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Doc));

                //XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
                //namespaces.Add(string.Empty, string.Empty);
                //serializer.Serialize(stream, doc, namespaces);

                serializer.Serialize(stream, doc);

                stream.Position = 0;
                using (var streamReader = new StreamReader(stream))
                {
                    xmlText = streamReader.ReadToEnd();
                }
            }

            return xmlText;
        }



    }
}
